#include "conv.h"
#include "utility.h"

#include <stdio.h>
#include <iostream>
#include <stdlib.h>
#include <iomanip>
#include <fstream>
#include <cmath>

#include <sstream>
#include <unistd.h>
#include <sys/types.h>
#include <vector>

using namespace std;

// Texture attachments.
const GLenum attach[] =
{ GL_COLOR_ATTACHMENT0_EXT, GL_COLOR_ATTACHMENT1_EXT };

int main(int argc, char ** argv)
{
    // Setiup parameters.
    const int size = 64;  // data array of dataSize by dataSize.
    const int cSize = 19; // Convolution function of size cSize by cSize.

    // GPU data arrays.
    vector<float4> h_data(size * size);
    vector<float4> h_result(size * size);
    vector<float4> h_cFunc(cSize * cSize);

    // Fill input data.
    generateData(size, &h_data[0]);
    saveResults("in", size, &h_data[0]);

    // Initialise GLUT and GLEW.
    initGLUT(argc, argv);
    initGLEW();

    // Initialise the FBO (need 2 as different size textures)
    GLuint fboId;
    glGenFramebuffersEXT(1, &fboId);
    glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fboId);

    // Set the view-port for 1:1 pixel : texel mapping.
    set1to1Mapping(cSize);

    // Convolution kernel.
    GLuint d_cFuncTex;
    setupTexture(&d_cFuncTex, cSize, attach[0], 0);

    //// Set the texture as render target ?? needed...?
    //glDrawBuffer(attach[0]);

    // Call Cg program to make the convolution function.
    makeConvFunc(cSize, d_cFuncTex);
    vector<float4> h_cResult(cSize * cSize);
    copyTextureFromGPU(attach[0], cSize, &h_cResult[0]);
    saveResults("kernel", cSize, &h_cResult[0]);


    // Data Texture
    GLuint d_dataTex;
    setupTexture(&d_dataTex, size, attach[0], &h_data[0]);

    // Result Texture.
    GLuint d_resultTex;
    setupTexture(&d_resultTex, size, attach[0], 0);

    // Set the view-port for 1:1 pixel : texel mapping.
    set1to1Mapping(size);

    // Setup the fragment shader and render a QUAD to do the processing.
    convolve(size, cSize, d_cFuncTex, d_dataTex, d_resultTex);

    // Copy back the result.
    copyTextureFromGPU(attach[0], size, &h_result[0]);
    saveResults("conv", size, &h_result[0]);
//    printData(size, &h_result[0], "result");

    // Cleanup.
    glDeleteTextures(1, &d_cFuncTex);
    glDeleteTextures(1, &d_dataTex);
    glDeleteTextures(1, &d_resultTex);
    glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
    glDeleteFramebuffersEXT(1, &fboId);

    return 0;
}





void generateData(const int size, float4 * h_data)
{
    for (int i = 0; i < size * size; i++)
    {
            h_data[i].r = 0.0f;
            h_data[i].g = 0.0f;
            h_data[i].b = 0.0f;
            h_data[i].a = 0.0f;
    }

    int x = 16, y = 16;
    h_data[y * size + x].r = 1.5f;
    x = 16, y = 26;
    h_data[y * size + x].r = 1.1f;
    x = 25, y = 40;
    h_data[y * size + x].r = 0.7f;
    x = 50, y = 5;
    h_data[y * size + x].r = 0.4f;
    x = 35, y = 30;
    h_data[y * size + x].r = 0.4f;
    x = 50, y = 50;
    h_data[y * size + x].r = -1.1f;
}


void makeConvFunc(const int cSize, const GLuint d_cFuncTex)
{
    // Setup the fragment shader and render a QUAD to do the processing.
    CGprofile profile;
    CGprogram program = 0;
    CGcontext context = 0;
    initCg("../kernels/convFunc.frag", "convFunc", profile, context, program);

    // Get the parameter handles by name.
    // This gets the named parameter "data" from the Cg fragment shader program
    // and stores it as a CGparamter called dataHandle.
    // Use these handles to connect data to the openGL texture.
    CGparameter d_csize = 0;
    d_csize = cgGetNamedParameter(program, "d_csize");
    cgSetParameter1i(d_csize, cSize);

    const float sigma2[4] = { 0.2f, 1.0f, 4.0f, 12.0f };
    CGparameter d_sigma2 = 0;
    d_sigma2 = cgGetNamedParameter(program, "d_sigma2");
    cgSetParameter4fv(d_sigma2, sigma2);

    // Bind fragment program (in this case set up to be conv.frag).
    cgGLBindProgram(program);

    // Enable fragment profile.
    cgGLEnableProfile(profile);

    // Render a filled quad of size corresponding to the data array being
    // processed.
    // note: This is when the processing is done!
    glQuad(cSize);

    // Disable the fragment profile as the processing is now done.
    cgGLDisableProfile(profile);
    cgGLUnloadProgram(program);
    cgDestroyContext(context);
}


/**
 * Render a full screen quad to render and therefore perform the fragment
 * shader calculation
 *
 * CG parameters are also set up here.
 */
void convolve(const int size, const int cSize, const GLuint d_cFuncTex,
        const GLuint d_dataTex, const GLuint d_resultTex)
{
    // Setup the fragment shader and render a QUAD to do the processing.
    CGprofile profile;
    CGprogram program = 0;
    CGcontext context = 0;
    initCg("../kernels/conv.frag", "convolve", profile, context, program);

    // Get the parameter handles by name.
    // This gets the named parameter "data" from the Cg fragment shader program
    // and stores it as a CGparamter called dataHandle.
    // Use these handles to connect data to the openGL texture.
    CGparameter d_csize = 0;
    d_csize = cgGetNamedParameter(program, "d_csize");
    cgSetParameter1i(d_csize, cSize);

    CGparameter d_size = 0;
    d_size = cgGetNamedParameter(program, "d_size");
    cgSetParameter1i(d_size, size);

    CGparameter d_cFunc = 0;
    d_cFunc = cgGetNamedParameter(program, "d_cFunc");
    cgGLSetTextureParameter(d_cFunc, d_cFuncTex);
    cgGLEnableTextureParameter(d_cFunc);

    CGparameter d_data = 0;
    d_data = cgGetNamedParameter(program, "d_data");
    cgGLSetTextureParameter(d_data, d_dataTex);
    cgGLEnableTextureParameter(d_data);

    // Bind fragment program (in this case set up to be conv.frag).
    cgGLBindProgram(program);

    // Enable fragment profile.
    cgGLEnableProfile(profile);

    // Render a filled quad of size corresponding to the data array being
    // processed.
    // note: This is when the processing is done!
    glQuad(size);

    // Disable the fragment profile as the processing is now done.
    cgGLDisableProfile(profile);
    cgGLUnloadProgram(program);
    cgDestroyContext(context);
}







/**
 * Prints out results of GPU calculations to a file after the textures have
 * been copied back to the CPU.
 *
 * gnuplot:
 *      splot "conv1.re" matrix w l
 */
void saveResults(string fileName, const int size, float4 * h_values)
{
    string sOutR = "data_" + fileName + ".r.z";
    string sOutG = "data_" + fileName + ".g.z";
    string sOutB = "data_" + fileName + ".b.z";
    string sOutA = "data_" + fileName + ".a.z";

    ofstream outR, outG, outB, outA;
    outR.open(sOutR.c_str());
    outG.open(sOutG.c_str());
    outB.open(sOutB.c_str());
    outA.open(sOutA.c_str());

    outR.precision(2);
    outG.precision(2);
    outB.precision(2);
    outA.precision(2);

    outR << "! nx " << size << " ny " << size;
    outR << " xmin " << 0 << " xmax " << size-1;
    outR << " ymin " << 0 << " ymax " << size-1;
    outR << endl;
    outG << "! nx " << size << " ny " << size;
    outG << " xmin " << 0 << " xmax " << size-1;
    outG << " ymin " << 0 << " ymax " << size-1;
    outG << endl;
    outB << "! nx " << size << " ny " << size;
    outB << " xmin " << 0 << " xmax " << size-1;
    outB << " ymin " << 0 << " ymax " << size-1;
    outB << endl;
    outA << "! nx " << size << " ny " << size;
    outA << " xmin " << 0 << " xmax " << size-1;
    outA << " ymin " << 0 << " ymax " << size-1;
    outA << endl;


    const int w = 7;
    for (int i = 0; i < size; i++)
    {
        for (int j = 0; j < size; j++)
        {
            const int idx = size * i + j;
            outR << fixed << setw(w) << left << h_values[idx].r;
            outG << fixed << setw(w) << left << h_values[idx].g;
            outB << fixed << setw(w) << left << h_values[idx].b;
            outA << fixed << setw(w) << left << h_values[idx].a;
        }
        outR << endl;
        outG << endl;
        outB << endl;
        outA << endl;

    }
    outR.close();
    outG.close();
    outB.close();
    outA.close();
}



void info()
{
    int maxColorAttachments = 0;
    glGetIntegerv(GL_MAX_COLOR_ATTACHMENTS_EXT, &maxColorAttachments);
    cout << "max colour attachments to FBO = " << maxColorAttachments << endl;

    int maxtexsize = 0;
    glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxtexsize);
    cout << "GL_MAX_TEXTURE_SIZE = " << maxtexsize << endl;
}

